﻿using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Linq;
using System.Threading;
using System.Windows.Input;
using Citrix.Cmp;
using Citrix.XaAdminMobile.Model;
using Citrix.XaAdminMobile.Util;

namespace Citrix.XaAdminMobile.ViewModel
{
    /// <summary>
    /// This is the v-m class for the sessions view. It is used to display a list of XenApp
    /// sessions to the user.
    /// </summary>
    internal class SessionsViewModel : INotifyPropertyChanged, IPage
    {
        /// <summary>
        /// The minimum number of characters needed before a search is performed.
        /// </summary>
        private const int MinSearchLength = 3;

        /// <summary>
        /// This is the time we wait after the user edits the search text before we kick off a
        /// search. This avoids spurious repeated searches while the user is still editing the
        /// search text.
        /// </summary>
        private const double SearchBackoffMs = 1000;

        /// <summary>
        /// A reference back to the root v-m object.
        /// </summary>
        private readonly ViewModelManager vmRoot;

        /// <summary>
        /// A reference to the associate model layer object that holds all the sessions.
        /// </summary>
        private readonly ISessionsModel sessionsModel;

        /// <summary>
        /// Whether or not search mode is active.
        /// </summary>
        private bool isSearchModeActive = false;

        /// <summary>
        /// Contains our session v-m objects mapped to their associated model objects. Used when
        /// processing model updates so we can quickly find the associated v-m object.
        /// </summary>
        private Dictionary<Model.ISessionModel, SessionViewModel> sessionMap = new Dictionary<ISessionModel, SessionViewModel>();

        /// <summary>
        /// This is the master list that is used to display sessions to the user. It contains
        /// sessions in sorted order. It will also optionally contain a search mode trigger item
        /// at the top of the list. When we are not in search mode, it will contain the trigger
        /// that the user can tap on to enter search mode. When we enter search mode we remove
        /// the trigger since it is not needed.
        /// </summary>
        private ObservableCollection<object> sessionsMaster = new ObservableCollection<object>();

        /// <summary>
        /// This is the search trigger item we dynamically add/remove from
        /// <see cref="sessionsMaster"/>.
        /// </summary>
        private readonly SearchTriggerViewModel searchVm;

        /// <summary>
        /// The command to reset sessions.
        /// </summary>
        private readonly DelegateCommand resetSessionsCommand;

        /// <summary>
        /// The command to refresh all session state.
        /// </summary>
        private readonly DelegateCommand refreshSessionsCommand;

        /// <summary>
        /// Command to cancel search mode.
        /// </summary>
        private readonly DelegateCommand cancelSearchModeCommand;

        /// <summary>
        /// Command to clear out all the search text.
        /// </summary>
        private readonly DelegateCommand clearSearchTextCommand;

        /// <summary>
        /// Toggles the item selection mode.
        /// </summary>
        private readonly DelegateCommand changeSelectionModeCommand;

        /// <summary>
        /// The current search text.
        /// </summary>
        private string searchInputText;

        /// <summary>
        /// Whether or not we are actively performing a search in a worker thread.
        /// </summary>
        private bool isSearching = false;

        /// <summary>
        /// Defines the selection mode we are in. When false selection will open the details
        /// view for that session. When true the selection status of the of the sessions can be
        /// toggled so that they may be reset.
        /// </summary>
        private bool isToggleSelectModeActive = false;

        /// <summary>
        /// Holds the search results.
        /// </summary>
        private ObservableCollection<object> searchResults = new ObservableCollection<object>();

        /// <summary>
        /// Used to sync access to internal state.
        /// </summary>
        private object stateLock = new object();

        /// <summary>
        /// This timer is used to delay automatic searching while the user is entering search
        /// input. This prevents the app from doing spurious searches while the user is entering
        /// their search terms.
        /// </summary>
        private System.Timers.Timer searchTimer = new System.Timers.Timer();

        /// <summary>
        /// The current search target that controls which fields are searched on.
        /// </summary>
        private SessionSearchOptions searchTarget = SessionSearchOptions.All;

        /// <summary>
        /// Used to display errors to the user.
        /// </summary>
        private ErrorStatusViewModel errorStatus;

        /// <summary>
        /// Used to display a loading popup.
        /// </summary>
        private LoadingViewModel loadingStatus;

        /// <summary>
        /// Used to track the selected session. In the tablet layout we restore the selected
        /// session when the user returns to browsing the sessions.
        /// </summary>
        private SessionViewModel selectedSession = null;

        /// <summary>
        /// Used to trigger the associated view to set focus to the search input textbox.
        /// </summary>
        public event EventHandler SetSearchFocus;

        /// <summary>
        /// Initialisation ctor.
        /// </summary>
        /// <param name="vmMgr">The root view-model object.</param>
        /// <param name="sessionsSource">The session source model object.</param>
        /// <exception cref="ArgumentNullException">vmMgr, sessionsSource</exception>
        public SessionsViewModel(ViewModelManager vmMgr, ISessionsModel sessionsSource)
        {
            if (null == vmMgr)
            {
                throw new ArgumentNullException("vmMgr");
            }
            if (null == sessionsSource)
            {
                throw new ArgumentNullException("sessionsSource");
            }

            this.vmRoot = vmMgr;
            this.sessionsModel = sessionsSource;
            this.errorStatus = new ErrorStatusViewModel();
            this.loadingStatus = new LoadingViewModel(() =>
            {
                return this.sessionsModel.IsLoading;
            });

            this.resetSessionsCommand = new DelegateCommand(() => ResetSessionsImp());
            this.refreshSessionsCommand = new DelegateCommand(() => RefreshSessionsImp());
            this.searchVm = new SearchTriggerViewModel(new DelegateCommand(() =>
                {
                    this.IsSearchModeActive = true;
                }));
            this.cancelSearchModeCommand = new DelegateCommand(() => this.IsSearchModeActive = false);
            this.clearSearchTextCommand = new DelegateCommand(() =>
                {
                    this.SearchInputText = string.Empty;
                    if (null != this.SetSearchFocus)
                    {
                        this.SetSearchFocus(this, EventArgs.Empty);
                    }
                });
            this.changeSelectionModeCommand = new DelegateCommand(() =>
            {
                this.IsToggleSelectActive = !this.IsToggleSelectActive;
                List<SessionViewModel> sessionCache;
                lock (this.stateLock)
                {
                    sessionCache = (from s in this.sessionMap.Values select s).ToList();
                }
                foreach (var s in sessionCache)
                {
                    s.NotifySelectionModeChanged();
                }
            });
            this.searchTimer.Elapsed += new System.Timers.ElapsedEventHandler(SearchTimer_Elapsed);
            this.searchTimer.Interval = SearchBackoffMs;
            this.searchTimer.AutoReset = false;

            this.sessionsModel.SessionsChanged += new EventHandler<SessionsChangedArgs>(SessionsModel_SessionsChanged);
            this.sessionsModel.PropertyChanged += new PropertyChangedEventHandler(SessionsModel_PropertyChanged);
        }

        /// <summary>
        /// Cancels search mode.
        /// </summary>
        public ICommand CancelSearchCommand
        {
            get { return this.cancelSearchModeCommand; }
        }

        /// <summary>
        /// Clears the input text for the search text box.
        /// </summary>
        public ICommand ClearSearchTextCommand
        {
            get { return this.clearSearchTextCommand; }
        }

        /// <summary>
        /// The error status for loading server state.
        /// </summary>
        public ErrorStatusViewModel ErrorStatus
        {
            get { return this.errorStatus; }
        }

        /// <summary>
        /// Gets the default session to display in the tablet layout. If a session was
        /// previously selected then that session will be returned. Otherwise we return the top
        /// most session that is still enabled.
        /// </summary>
        /// <returns>A default session to display in the right pane of the tablet layout. Maybe
        /// null if no session is found.</returns>
        public SessionViewModel GetDefaultSession()
        {
            // UI thread only since we access sessionsMaster which is an ObservableCollection.
            this.vmRoot.AssertUiThread();

            lock (this.stateLock)
            {
                if (this.selectedSession != null)
                {
                    // Check that the selected session still exists in our master map.
                    if (this.sessionMap.ContainsKey(this.selectedSession.Model))
                    {
                        return this.selectedSession;
                    }
                    else
                    {
                        this.selectedSession = null;
                    }
                }
            }

            // If there is no existing selected session we just return the current top session.
            foreach (object s in this.sessionsMaster)
            {
                // Ignore the search trigger, and ignore any sessions that are not enabled.
                SessionViewModel session = s as SessionViewModel;
                if (null != session && session.IsEnabled)
                {
                    return session;
                }
            }
            return null;
        }

        /// <summary>
        /// Whether the search all option is selected.
        /// </summary>
        public bool IsSearchAllChecked
        {
            get
            {
                lock (this.stateLock)
                {
                    return this.searchTarget == SessionSearchOptions.All;
                }
            }
            set
            {
                this.vmRoot.AssertUiThread();

                bool changed = false;
                lock (this.stateLock)
                {
                    // We only handle the set true case. The other search properties getting set
                    // true will flip the result of this property to false.
                    if (value && this.searchTarget != SessionSearchOptions.All)
                    {
                        changed = true;
                        this.searchTarget = SessionSearchOptions.All;
                    }
                }

                if (changed)
                {
                    ThreadPool.QueueUserWorkItem(state => DoSearch());
                    OnPropertyChanged("IsSearchAllChecked");
                    OnPropertyChanged("IsSearchUserNameChecked");
                    OnPropertyChanged("IsSearchAppNameChecked");
                }
            }
        }

        /// <summary>
        /// Whether the search user name option is selected.
        /// </summary>
        public bool IsSearchUserNameChecked
        {
            get
            {
                lock (this.stateLock)
                {
                    return this.searchTarget == SessionSearchOptions.User;
                }
            }
            set
            {
                this.vmRoot.AssertUiThread();

                bool changed = false;
                lock (this.stateLock)
                {
                    // We only handle the set true case. The other search properties getting set
                    // true will flip the result of this property to false.
                    if (value && this.searchTarget != SessionSearchOptions.User)
                    {
                        changed = true;
                        this.searchTarget = SessionSearchOptions.User;
                    }
                }

                if (changed)
                {
                    ThreadPool.QueueUserWorkItem(state => DoSearch());
                    OnPropertyChanged("IsSearchAllChecked");
                    OnPropertyChanged("IsSearchUserNameChecked");
                    OnPropertyChanged("IsSearchAppNameChecked");
                }
            }
        }

        /// <summary>
        /// Whether the search app name option is selected.
        /// </summary>
        public bool IsSearchAppNameChecked
        {
            get
            {
                lock (this.stateLock)
                {
                    return this.searchTarget == SessionSearchOptions.App;
                }
            }
            set
            {
                this.vmRoot.AssertUiThread();

                bool changed = false;
                lock (this.stateLock)
                {
                    // We only handle the set true case. The other search properties getting set
                    // true will flip the result of this property to false.
                    if (value && this.searchTarget != SessionSearchOptions.App)
                    {
                        changed = true;
                        this.searchTarget = SessionSearchOptions.App;
                    }
                }

                if (changed)
                {
                    ThreadPool.QueueUserWorkItem(state => DoSearch());
                    OnPropertyChanged("IsSearchAllChecked");
                    OnPropertyChanged("IsSearchUserNameChecked");
                    OnPropertyChanged("IsSearchAppNameChecked");
                }
            }
        }

        /// <summary>
        /// Whether the search server name option is selected.
        /// </summary>
        public bool IsSearchServerChecked
        {
            get
            {
                lock (this.stateLock)
                {
                    return this.searchTarget == SessionSearchOptions.Server;
                }
            }
            set
            {
                this.vmRoot.AssertUiThread();

                bool changed = false;
                lock (this.stateLock)
                {
                    // We only handle the set true case. The other search properties getting set
                    // true will flip the result of this property to false.
                    if (value && this.searchTarget != SessionSearchOptions.Server)
                    {
                        changed = true;
                        this.searchTarget = SessionSearchOptions.Server;
                    }
                }

                if (changed)
                {
                    ThreadPool.QueueUserWorkItem(state => DoSearch());
                    OnPropertyChanged("IsSearchAllChecked");
                    OnPropertyChanged("IsSearchUserNameChecked");
                    OnPropertyChanged("IsSearchAppNameChecked");
                }
            }
        }

        /// <summary>
        /// Whether or not search mode is active. When active we display a special search
        /// toolbar.
        /// </summary>
        public bool IsSearchModeActive
        {
            get
            {
                return this.isSearchModeActive;
            }
            private set
            {
                this.vmRoot.AssertUiThread();

                if (this.isSearchModeActive != value)
                {
                    this.IsSearchAllChecked = true;
                    this.isSearchModeActive = value;
                    this.searchInputText = string.Empty;

                    OnPropertyChanged("IsSearchModeActive");
                    OnPropertyChanged("SessionsMaster");
                    OnPropertyChanged("ShowSearchResults");
                    OnPropertyChanged("SearchInputText");
                }
            }
        }

        /// <summary>
        /// Returns the state of the toggle selection mode. By default it is disabled and
        /// selecting an item will show the details for the session. When enabled selecting a
        /// session will mark it as "selected" in order to perform a reset on it.
        /// </summary>
        public bool IsToggleSelectActive
        {
            get
            {
                return this.isToggleSelectModeActive;
            }
            set
            {
                if (value != this.isToggleSelectModeActive)
                {
                    this.isToggleSelectModeActive = value;
                    OnPropertyChanged("IsToggleSelectActive");
                    OnPropertyChanged("ToggleSelectionModeLabel");
                }
            }
        }

        /// <summary>
        /// Whether or not the user has entered any search text.
        /// </summary>
        public bool HaveNoSearchInputText
        {
            get
            {
                lock (this.stateLock)
                {
                    return string.IsNullOrEmpty(this.searchInputText);
                }
            }
        }

        /// <summary>
        /// Returns the loading status view-model.
        /// </summary>
        public LoadingViewModel LoadingStatus
        {
            get { return this.loadingStatus; }
        }

        /// <summary>
        /// The master navigation toolbar v-m.
        /// </summary>
        public NavToolbarViewModel NavToolbarViewModel
        {
            get { return this.vmRoot.NavToolbarViewModel; }
        }

        /// <summary>
        /// Called by a session to notify us that the user has selected this session to view its
        /// details.
        /// </summary>
        /// <param name="session">The newly selected session.</param>
        public void NotifySessionSelected(SessionViewModel session)
        {
            this.selectedSession = session;
            OnPropertyChanged("SelectedItem");
        }

        /// <summary>
        /// Bound to the search input text field on the UI.
        /// </summary>
        public string SearchInputText
        {
            get
            {
                lock (this.stateLock)
                {
                    return this.searchInputText;
                }
            }
            set
            {
                this.vmRoot.AssertUiThread();

                bool changed = false;
                lock (this.stateLock)
                {
                    if (!string.Equals(this.searchInputText, value, StringComparison.OrdinalIgnoreCase))
                    {
                        changed = true;
                        this.searchInputText = value;

                        //
                        // We need at least 3 chars to start a search, if we have less than this we
                        // don't display any search results.
                        //
                        if (HaveValidSearchInput)
                        {
                            if (!this.isSearching)
                            {
                                // Reset the search timer.
                                this.searchTimer.Stop();
                                this.searchTimer.Start();
                            }
                        }
                        else
                        {
                            this.searchResults.Clear();
                        }
                    }
                }

                if (changed)
                {
                    OnPropertyChanged("SearchInputText");
                    OnPropertyChanged("HaveNoSearchInputText");
                    OnPropertyChanged("ShowSearchResults");

                }
            }
        }

        /// <summary>
        /// Used to toggle the visibility of our session list and search results list. We toggle
        /// between two lists to provide a fast transition for the user. If we used one listbox
        /// and swapped its contents the user would get a noticeable lag when the lists have more
        /// than a few items.
        /// </summary>
        /// <remarks>
        /// We show the search results when the user has entered 3 or more chars of search text.
        /// Otherwise we just display the full list of sessions.
        /// </remarks>
        public bool ShowSearchResults
        {
            get
            {
                return this.isSearchModeActive && HaveValidSearchInput;
            }
        }

        /// <summary>
        /// The session list. Contains all the known sessions as well as an optional search
        /// trigger. The search trigger is held at the top of the list when we are not in search
        /// mode. We use it to display an item in the session list the user taps on to trigger
        /// search mode.
        /// </summary>
        public ObservableCollection<object> SessionsMaster
        {
            get
            {
                this.vmRoot.AssertUiThread();

                bool haveTrigger = this.sessionsMaster.Count > 0 && this.sessionsMaster[0] is SearchTriggerViewModel;

                //
                // Do a just-in-time addition or removal of the special search trigger at the
                // top of the list. We only show the search trigger to enter search mode when
                // search mode is not active and we have some sessions to search.
                //
                if (!this.IsSearchModeActive && this.SessionCount > 0)
                {
                    if (!haveTrigger)
                    {
                        this.sessionsMaster.Insert(0, this.searchVm);
                    }
                }
                else
                {
                    if (haveTrigger)
                    {
                        this.sessionsMaster.RemoveAt(0);
                    }
                }

                return this.sessionsMaster;
            }
        }

        /// <summary>
        /// The search results.
        /// </summary>
        public ObservableCollection<object> SearchResults
        {
            get { return this.searchResults; }
        }

        /// <summary>
        /// Returns the currently selected session. This is used to trigger a background
        /// highlight for this session in the session list.
        /// </summary>
        public SessionViewModel SelectedItem
        {
            get
            {
                //
                // We only want the highlight in the tablet layout, so return null for the phone
                // layout. Generally we would create separate views for phone vs tablet, but I
                // chose to put the exception here in the v-m since it was such a minor
                // difference to create two views for.
                //
                if (this.vmRoot.ActiveNavigation is TabletNavigation)
                {
                    return this.selectedSession;
                }
                else
                {
                    return null;
                }
            }
        }

        /// <summary>
        /// The count of known XenApp sessions.
        /// </summary>
        public int SessionCount
        {
            get
            {
                lock (this.stateLock)
                {
                    return this.sessionMap.Count;
                }
            }
        }

        /// <summary>
        /// Used by the view to request the v-m to start searching with the current search
        /// text.
        /// </summary>
        public void StartSearch()
        {
            if (HaveValidSearchInput)
            {
                ThreadPool.QueueUserWorkItem(state => DoSearch());
            }
        }

        /// <summary>
        /// The label to display on the button that toggles the selection mode.
        /// </summary>
        public string ToggleSelectionModeLabel
        {
            get
            {
                if (this.isToggleSelectModeActive)
                {
                    return Properties.Resources.DisableToggleSelectionLabel;
                }
                else
                {
                    return Properties.Resources.EnableToggleSelectionLabel;
                }
            }
        }

        /// <summary>
        /// Refresh sessions command. Triggers a full refresh of session state from the XenApp
        /// server.
        /// </summary>
        public ICommand RefreshSessionsCommand
        {
            get { return this.refreshSessionsCommand; }
        }

        /// <summary>
        /// Reset session command.
        /// </summary>
        public ICommand ResetSessionsCommand
        {
            get { return this.resetSessionsCommand; }
        }

        /// <summary>
        /// Command to toggle the selection mode.
        /// </summary>
        public ICommand ToggleSelectionModeCommand
        {
            get { return this.changeSelectionModeCommand;  }
        }

        /// <summary>
        /// Refresh command implementation.
        /// </summary>
        private void RefreshSessionsImp()
        {
            this.sessionsModel.Refresh();
        }

        /// <summary>
        /// Whether or not we have sufficient search text to start a search.
        /// </summary>
        private bool HaveValidSearchInput
        {
            get
            {
                this.vmRoot.AssertUiThread();

                lock (this.stateLock)
                {
                    return !string.IsNullOrEmpty(this.searchInputText) &&
                        this.searchInputText.Length >= MinSearchLength;
                }
            }
        }

        /// <summary>
        /// Executes a search.
        /// </summary>
        private void DoSearch()
        {
            this.searchTimer.Stop();

            string currentText;
            SessionSearchOptions fieldsToSearch;
            List<SessionViewModel> workingSet;
            lock (this.stateLock)
            {
                // Ensure we only run one search at a time.
                if (this.isSearching)
                {
                    return;
                }

                this.isSearching = true;
                currentText = this.searchInputText;
                fieldsToSearch = this.searchTarget;
                workingSet = new List<SessionViewModel>(this.sessionMap.Values);
            }

            try
            {
                // Do the search. We start with all sessions and remove all the ones that are
                // not a match for the search input.
                for (int i = workingSet.Count - 1; i >= 0; --i)
                {
                    var s = workingSet[i];
                    if (!s.Match(currentText, fieldsToSearch))
                    {
                        workingSet.RemoveAt(i);
                    }
                }

                // Pass the results over to the UI.
                this.vmRoot.UiDispatch(() =>
                    {
                        this.searchResults.Clear();
                        SortedAdd(this.searchResults, workingSet);
                    });
            }
            finally
            {
                //
                // Cleanup state, and kickoff a new search if required.
                //
                bool startNewSearch = false;
                lock (this.stateLock)
                {
                    this.isSearching = false;

                    // Has our search text/target changed?
                    startNewSearch =
                        (0 != string.CompareOrdinal(currentText, this.searchInputText)) ||
                        (fieldsToSearch != this.searchTarget);
                }
                if (startNewSearch)
                {
                    ThreadPool.QueueUserWorkItem(state => DoSearch());
                }
            }
        }

        /// <summary>
        /// Reset command implementation.
        /// </summary>
        private void ResetSessionsImp()
        {
            // Find all the selected sessions.
            List<ISessionModel> selected = new List<ISessionModel>();
            lock (this.stateLock)
            {
                foreach (var kvp in this.sessionMap)
                {
                    if (kvp.Value.IsSelectedForEdit && kvp.Value.IsEnabled)
                    {
                        selected.Add(kvp.Value.Model);
                    }
                }
            }

            // Request the selected sessions to be reset.
            this.sessionsModel.Reset(selected);
        }

        /// <summary>
        /// Handler for the model object properties changing.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void SessionsModel_PropertyChanged(object sender, PropertyChangedEventArgs e)
        {
            if (e.PropertyName == "IsLoading")
            {
                this.loadingStatus.NotifyChanged();
            }
            else if (e.PropertyName == "LastLoadError")
            {
                this.vmRoot.UiDispatch(() => this.errorStatus.ErrorMessage = this.sessionsModel.LastLoadError);
            }
        }

        /// <summary>
        /// Event handler for sessions being added and/or removed.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void SessionsModel_SessionsChanged(object sender, SessionsChangedArgs e)
        {
            List<SessionViewModel> removedVmInstances = new List<SessionViewModel>();
            List<SessionViewModel> addedVmInstances = new List<SessionViewModel>();

            //
            // Use our session map to determine whether we need to add/remove session v-m
            // instances, and to get/create the relevant v-m instances.
            //
            lock (this.stateLock)
            {
                foreach (var removed in e.RemovedSessions)
                {
                    if (this.sessionMap.ContainsKey(removed))
                    {
                        removedVmInstances.Add(this.sessionMap[removed]);
                        this.sessionMap.Remove(removed);
                    }
                }

                foreach (var added in e.AddedSessions)
                {
                    var vm = new SessionViewModel(this.vmRoot, added);
                    this.sessionMap[added] = vm;
                    addedVmInstances.Add(vm);
                }
            }

            this.vmRoot.UiDispatch(() =>
            {
                //
                // Now we have the v-m instances we can remove/add them to the master session
                // list.
                //

                foreach (var s in removedVmInstances)
                {
                    this.sessionsMaster.Remove(s);
                }

                SortedAdd(this.sessionsMaster, addedVmInstances);

                OnPropertyChanged("SessionsMaster");
            });
        }

        /// <summary>
        /// Event handler for the search timer expiring. This means the user has stopped editing
        /// the search text, so we can now execute a search.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void SearchTimer_Elapsed(object sender, System.Timers.ElapsedEventArgs e)
        {
            ThreadPool.QueueUserWorkItem(state => DoSearch());
        }

        /// <summary>
        /// Adds sessions from the source set to the desintation list in sorted order. The sort
        /// order is account name then session ID.
        /// </summary>
        /// <param name="destination">The destination list to insert into. Must not be null.</param>
        /// <param name="source">The source list we are inserting. Must not be null.</param>
        private static void SortedAdd(ObservableCollection<object> destination, IEnumerable<SessionViewModel> source)
        {
            foreach (var s in source)
            {
                // Add the new v-m instances in sort order.
                bool inserted = false;
                for (int i = 0; i < destination.Count; ++i)
                {
                    var current = destination[i] as SessionViewModel;
                    if (null != current)
                    {
                        int compareResult = s.Compare(current);
                        if (compareResult < 0)
                        {
                            destination.Insert(i, s);
                            inserted = true;
                            break;
                        }
                    }
                }

                // Finally add to the end if the search order is greater than all current
                // sessions.
                if (!inserted)
                {
                    destination.Add(s);
                }
            }
        }

        #region INotifyPropertyChanged Members

        /// <summary>
        /// Fired whenever a public property changes.
        /// </summary>
        public event PropertyChangedEventHandler PropertyChanged;

        /// <summary>
        /// Fires the property change event.
        /// </summary>
        /// <param name="propertyName">The name of the property that changed.</param>
        private void OnPropertyChanged(string propertyName)
        {
            NotifyHelper.VerifyProperty(propertyName, this);
            if (this.PropertyChanged != null)
            {
                this.PropertyChanged.Raise(this, new PropertyChangedEventArgs(propertyName));
            }
        }

        #endregion // INotifyPropertyChanged Members

        #region IPage Members

        /// <summary>
        /// Used to notify the page when is made the active page, or when it has become inactive.
        /// </summary>
        /// <param name="isActive">True if this page is now active, false if it has become
        /// inactive.</param>
        public void NotifyActiveChanged(bool isActive)
        {
            if (!isActive)
            {
                // The keyboard may still be visible from doing a search, so ensure it is hidden
                // when this page is closed.
                this.vmRoot.App.CmpWpfApi.Input.BeginHideKeyboard(null);
            }
        }

        /// <summary>
        /// Notifies the active page that the physical back button has been pressed on the
        /// client device.
        /// </summary>
        public void NotifyBackButtonPressed()
        {
        }

        /// <summary>
        /// Used to access the back command that will navigate you from the current page to the
        /// previous page in the navigation history.
        /// </summary>
        public ViewModelManager VmManager
        {
            get { return this.vmRoot; }
        }

        #endregion
    }
}
